﻿using System;
using System.Collections.Generic;
using System.IO;

using UnityEngine;

namespace LFAsset.Runtime
{
    public static class Versions
    {
        public const string DATA_NAME = "res";
        public const string FILE_NAME = "ver";

        private static readonly VDisk _disk = new VDisk();
		private static readonly Dictionary<string, VFile> _update_files = new Dictionary<string, VFile>();
        private static readonly Dictionary<string, VFile> _base_files = new Dictionary<string, VFile>();

        public static AssetBundle LoadAssetBundleFromFile(string url)
        {
            if(!File.Exists(url))
            {
                if(_disk != null)
                {
                    var name = Path.GetFileName(url);
                    var file = _disk.GetFile(name);
                    if(file != null)
                    {
                        return AssetBundle.LoadFromFile(_disk.name, 0, (ulong)file.offset);
                    }
                }
            }

            return AssetBundle.LoadFromFile(url);
        }

        public static bool HasFile(string url)
        {
            if (File.Exists(url))
            {
                return true;
            }

            if (_disk != null)
            {
                var name = Path.GetFileName(url);
                var file = _disk.GetFile(name);
                if (file != null)
                {
                    return true;
                }
            }

            return false;
        }

        public static void BuildVersions(string outputPath, string[] bundles, int version)
        {
            var filePath = $"{outputPath}/{FILE_NAME}";
            if(File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            var dataPath = $"{outputPath}/{DATA_NAME}";
            if(File.Exists(dataPath))
            {
                File.Delete(dataPath);
            }

            var disk = new VDisk();
            foreach (var file in bundles)
            {
                using(var fs = File.OpenRead($"{outputPath}/{file}"))
                {
                    disk.AddFile(file, fs.Length, CRC32Helper.GetCRC32Hash(fs));
                }
            }

            disk.name = dataPath;
            disk.Save();
            using(var stream = File.OpenWrite(filePath))
            {
                var writer = new BinaryWriter(stream);
                writer.Write(version);
                writer.Write(disk.files.Count + 1);
                using(var fs = File.OpenRead(dataPath))
                {
                    var file = new VFile() { name = DATA_NAME, len = fs.Length, hash = CRC32Helper.GetCRC32Hash(fs) };
                    file.Serialize(writer);
                }

                foreach (var file in disk.files)
                {
                    file.Serialize(writer);
                }
            }
        }

		public static int LoadVersion(string fileName)
        {
            if(!File.Exists(fileName))
            {
                return -1;
            }
            try
            {
                using(var stream = File.OpenRead(fileName))
                {
                    var reader = new BinaryReader(stream);
                    return reader.ReadInt32();
                }
            }
            catch(Exception e)
            {
                Debug.LogException(e);
                return -1;
            }
        }

        public static List<VFile> LoadVersions(string fileName, bool update = false)
        {
            var data = update ? _update_files : _base_files;
            data.Clear();
            using(var stream = File.OpenRead(fileName))
            {
                var reader = new BinaryReader(stream);
                var list = new List<VFile>();
                var ver = reader.ReadInt32();
                var count = reader.ReadInt32();
                for(int i = 0; i < count; i++)
                {
                    var version = new VFile();
                    version.Deserialize(reader);
                    list.Add(version);
                    data[version.name] = version;
                }

                return list;
            }
        }

        public static bool LoadDisk(string filename)
        {
            return _disk.Load(filename);
        }

        public static void UpdateDisk(string savePath, List<VFile> newFiles)
        {
            var saveFiles = new List<VFile>();
            var files = _disk.files;
            foreach (var file in files)
            {
                if(_update_files.ContainsKey(file.name))
                {
                    saveFiles.Add(file);
                }
            }
            _disk.Update(savePath, newFiles, saveFiles);
        }

		public static bool IsNew(string path, long len, string hash)
        {
            VFile file;
            var key = Path.GetFileName(path);
            if(_base_files.TryGetValue(key, out file))
            {
                if(key.Equals(DATA_NAME) || (file.len == len && file.hash.Equals(hash, StringComparison.OrdinalIgnoreCase)))
                {
                    return false;
                }
            }

            if(_disk.Exists())
            {
                var vdf = _disk.GetFile(path);
                if(vdf != null && vdf.len == len && vdf.hash.Equals(hash, StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }
            }

            if(!File.Exists(path))
            {
                return true;
            }

            using(var stream = File.OpenRead(path))
            {
                if(stream.Length != len)
                {
                    return true;
                }

                return !CRC32Helper.GetCRC32Hash(stream).Equals(hash, StringComparison.OrdinalIgnoreCase);
            }
        }
    }
}
